#pragma once
#include <include.hpp>

namespace ret
{
	extern "C" void* spoofer_stub();

	namespace detail
	{
		template <typename Ret, typename... Args>
		static __forceinline auto shellcode_stub_helper(
			const void* shell,
			Args... args
		) -> Ret
		{
			auto fn = (Ret(*)(Args...))(shell);
			return fn(args...);
		}

		template <size_t Argc, typename>
		struct argument_remapper
		{
			// At least 5 params
			template<
				typename Ret,
				typename First,
				typename Second,
				typename Third,
				typename Fourth,
				typename... Pack
			>
			static auto do_call(
				const void* shell,
				void* shell_param,
				First first,
				Second second,
				Third third,
				Fourth fourth,
				Pack... pack
			) -> Ret
			{
				return shellcode_stub_helper<
					Ret,
					First,
					Second,
					Third,
					Fourth,
					void*,
					void*,
					Pack...
				>(
					shell,
					first,
					second,
					third,
					fourth,
					shell_param,
					nullptr,
					pack...
				);
			}
		};

		template <size_t Argc>
		struct argument_remapper<Argc, std::enable_if_t<Argc <= 4>>
		{
			// 4 or less params
			template<
				typename Ret,
				typename First = void*,
				typename Second = void*,
				typename Third = void*,
				typename Fourth = void*
			>
			static auto do_call(
				const void* shell,
				void* shell_param,
				First first = First{},
				Second second = Second{},
				Third third = Third{},
				Fourth fourth = Fourth{}
			) -> Ret
			{
				return shellcode_stub_helper<
					Ret,
					First,
					Second,
					Third,
					Fourth,
					void*,
					void*
				>(
					shell,
					first,
					second,
					third,
					fourth,
					shell_param,
					nullptr
				);
			}
		};
	}

	void* trampoline_gadget = nullptr;

	template <typename Ret, typename... Args>
	static __forceinline auto spoof_call(
		Ret(*fn)(Args...),
		Args... args
	) -> Ret
	{
		struct shell_params
		{
			const void* trampoline;
			void* function;
			void* rsi;
		};

		shell_params p{ trampoline_gadget, reinterpret_cast<void*>(fn) };
		using mapper = detail::argument_remapper<sizeof...(Args), void>;
		return mapper::template do_call<Ret, Args...>((const void*)&spoofer_stub, &p, args...);
	}
}
